package pri.hillchen.std.nio.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author:hillchen
 * @DESCRIPTION:
 * @Date: Created in 16:47 2018/3/6 0006.
 */
public class ServerChannel {
    private int port = 8080;
    public static Charset charset = Charset.forName("UTF-8");
    public static String USER_EXIST = "系统提示：该昵称已经存在，请换一个昵称";
    public static String REGIST_PREFIX = "REGSTR|";
    public static String REGIST_SUFFIX = "|REGEND";
    private Selector selector = null;
    /**
     * 客户端连接和客户端用户间的名称连接
     */
    private ConcurrentHashMap<SocketChannel,String> clientNames = new ConcurrentHashMap<SocketChannel, String>();
    private HashSet<String> users = new HashSet<String>();


    public ServerChannel(int port) throws IOException {
        this.port = port;
        //要想富，先修路
        //先把通道打开
        ServerSocketChannel server = ServerSocketChannel.open();

        //设置高速公路的关卡
        server.bind(new InetSocketAddress(this.port));
        server.configureBlocking(false);


        //开门迎客，排队叫号大厅开始工作
        selector = Selector.open();

        //告诉服务叫号大厅的工作人员，你可以接待了（事件）
        server.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务已启动，监听端口是：" + this.port);
    }

    public void registUser(String userName,SocketChannel channel) throws IOException {
        if(users.contains(userName)){
            channel.write(charset.encode(USER_EXIST));
        }else{
            users.add(userName);
            clientNames.put(channel,userName);
            String message = "欢迎 " + userName + " 进入聊天室! 当前在线人数:" + users.size();
            broadCast(null, message);
        }

    }

    /**
     * 连接请求处理
     * @param key
     * @throws IOException
     */
    public void doAccept(SelectionKey key) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel)key.channel();
        SocketChannel client = server.accept();
        //非阻塞模式
        client.configureBlocking(false);
        //注册选择器，并设置为读取模式，收到一个连接请求，然后起一个SocketChannel，并注册到selector上，之后这个连接的数据，就由这个SocketChannel处理
        client.register(selector, SelectionKey.OP_READ);

        //将此对应的channel设置为准备接受其他客户端请求
        key.interestOps(SelectionKey.OP_ACCEPT);
        /**
         * 连接成功后，提示用户设定用户名并登陆
         */
        client.write(charset.encode("请输入你的昵称"));
    }
    public void doRead(SelectionKey key) throws IOException {
        //返回该SelectionKey对应的 Channel，其中有数据需要读取
        SocketChannel client = (SocketChannel)key.channel();
        String msg = null;
        try{
            msg = getMsg(client);
            //将此对应的channel设置为准备下一次接受数据
            key.interestOps(SelectionKey.OP_READ);
        }catch (IOException io){
            key.cancel();
            if(key.channel() != null){
                key.channel().close();
                return;
            }
        }
        if(msg!=null){
            if(!checkIsRegist(client,msg)){
                broadCast(client, msg);
            }
        }
        client.register(selector, SelectionKey.OP_READ);
    }

    private boolean checkIsRegist(SocketChannel client ,String msg) throws IOException {
        if(msg!= null && msg.startsWith(REGIST_PREFIX) && msg.endsWith(REGIST_SUFFIX)){
            String userName = msg.substring(REGIST_PREFIX.length(),msg.length() - REGIST_SUFFIX.length());
            registUser(userName,client);
            return true;
        }
        return false;
    }
    /**
     * 读取管道中的消息
     * @param client
     * @return
     * @throws IOException
     */
    private String getMsg(SocketChannel client) throws IOException {
        //往缓冲区读数据
        ByteBuffer buff = ByteBuffer.allocate(1024);
        StringBuilder content = new StringBuilder();
        while(client.read(buff) > 0){
            buff.flip();
            content.append(charset.decode(buff));
        }
        return content.toString();
    }

    public void listener() throws IOException{
        while(true) {

            //在轮询，我们服务大厅中，到底有多少个人正在排队
            int wait = selector.select();
            //如果没有人排队，进入下一次轮询
            if(wait == 0) {
                continue;
            }
            //取号，默认给他分配个号码（排队号码）
            Set<SelectionKey> keys = selector.selectedKeys();  //可以通过这个方法，知道可用通道的集合
            Iterator<SelectionKey> iterator = keys.iterator();
            while(iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //处理一个，号码就要被消除，打发他走人（别在服务大厅占着茅坑不拉屎了）
                //过号不候
                iterator.remove();
                //处理逻辑
                process(key);
            }
        }
    }

    public void process(SelectionKey key) throws IOException {
        //判断客户端确定已经进入服务大厅并且已经可以实现交互了
        if(key.isAcceptable()){
            doAccept(key);
        }else if(key.isReadable()){//处理来自客户端的数据读取请求
            //返回该SelectionKey对应的 Channel，其中有数据需要读取
            doRead(key);
        }
    }
    public void broadCast(SocketChannel client, String content) throws IOException {
        //广播数据到所有的SocketChannel中
        for(SocketChannel targetchannel : clientNames.keySet()) {
            //如果client不为空，不回发给发送此内容的客户端
            if(targetchannel != client) {
                SocketChannel target = targetchannel;
                if(client!=null){
                    target.write(charset.encode(clientNames.get(client)+":"+content));
                }else{
                    target.write(charset.encode(content));
                }
            }
        }
    }


    public static void main(String[] args) throws IOException {
        new ServerChannel(8080).listener();
    }


}
